package com.mulong.common.aop;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;

import jakarta.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;

import com.alibaba.fastjson2.JSON;
import com.mulong.common.util.RequestContext;

import lombok.extern.slf4j.Slf4j;

/**
 * GlobalApiAspect
 * 
 * @author mulong
 * @date 2021-03-06 01:00:15
 */
@ConditionalOnProperty(value = "mulong.global.api.aop.enabled", havingValue = "true", matchIfMissing = true)
@Slf4j
@Aspect
@Component
public class GlobalApiAspect {

    @Pointcut("execution(* com.mulong..*.web.api..*Api.*(..))")
    public void api() {
    }

    @Before(value = "api()")
    public void before(JoinPoint joinPoint) {
        RequestContext.setBeginTime(System.currentTimeMillis());
        HttpServletRequest request = RequestContext.getRequest();
        String ip = request.getRemoteAddr();
        String reqMethod = request.getMethod();
        String url = parseUrl(request);
        String body = parseBody(joinPoint);
        String bodyFormat = StringUtils.EMPTY;
        if (body != null) {
            bodyFormat = "body: " + body;
        }
        log.info("Request >> ip [{}] method [{}] url [{}] {}", ip, reqMethod, url, bodyFormat);
    }

    @AfterReturning(value = "api()", returning = "res")
    public void afterReturning(Object res) {
        logResponse(res);
    }

    private String parseUrl(HttpServletRequest request) {
        String url = request.getRequestURI();
        String params = getParamString(request);
        if (StringUtils.isNotBlank(params)) {
            if (!url.endsWith("?")) {
                url = url + "?";
            }
            url = url + params;
        }
        return url;
    }

    private String getParamString(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (parameterMap == null || parameterMap.size() == 0) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String key = entry.getKey();
            String[] value = entry.getValue();
            if (value != null && value.length > 0) {
                sb.append(key).append("=").append(value[0]).append("&");
            }
        }
        sb.deleteCharAt(sb.length()-1);
        return sb.toString();
    }

    private String parseBody(JoinPoint joinPoint) {
        String body = null;
        Object[] args = joinPoint.getArgs();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Annotation annotation = parameter.getDeclaredAnnotation(RequestBody.class);
            if (annotation != null) {
                if (String.class.equals(parameter.getType())) {
                    body = (String) args[i];
                } else {
                    body = JSON.toJSONString(args[i]);
                }
                break;
            }
        }
        return body;
    }

    private void logResponse(Object res) {
        if (res == null) {
            return;
        }
        long beginTime = RequestContext.getBeginTime();
        long useTime = System.currentTimeMillis() - beginTime;
        String response = JSON.toJSONString(res);
        if (StringUtils.isBlank(response) || response.length() > 5000) {
            log.info("Response << time: {} - data: too long or empty, don't show", useTime);
        } else {
            log.info("Response << time: {} - data: {}", useTime, response);
        }
    }

}
